/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "host/commands/cvd/server_command/help.h"

#include <mutex>

#include "common/libs/fs/shared_buf.h"
#include "host/commands/cvd/command_sequence.h"
#include "host/commands/cvd/server.h"
#include "host/commands/cvd/server_command/utils.h"
#include "host/commands/cvd/types.h"

namespace cuttlefish {

static constexpr char kHelpMessage[] = R"(Cuttlefish Virtual Device (CVD) CLI.

usage: cvd <selector/driver options> <command> <args>

Selector Options:
  -group_name <name>     Specify the name of the instance group created
                         or selected.
  -instance_name <name>  Selects the device of the given name to perform the
                         commands for.
  -instance_name <names> Takes the names of the devices to create within an
                         instance group. The 'names' is comma-separated.

Driver Options:
  -help                  Print this message
  -verbosity=<LEVEL>     Adjust Cvd verbosity level. LEVEL is Android log
                         severity. (Required: cvd >= v1.3)
  -acquire_file_lock     If the flag is given, the cvd server attempts to
                         acquire the instance lock file lock. (default: true)

Commands:
  help                   Print this message.
  help <command>         Print help for a command.
  start                  Start a device.
  stop                   Stop a running device.
  clear                  Stop all running devices and delete all instance and
                         assembly directories.
  fleet                  View the current fleet status.
  kill-server            Kill the cvd_server background process.
  server-kill            Same as kill-server
  powerwash              Delivers powerwash command to the selected device
  restart                Restart the device without reinitializing the disks
  restart-server         Restart the cvd_server background process.
  status                 Check and print the state of a running instance.
  env                    Control the environment of running instances like wifi
                         or geolocation.
  host_bugreport         Capture a host bugreport, including configs, logs, and
                         tombstones.

Args:
  <command args>         Each command has its own set of args.
                         See cvd help <command>.

Experimental:
  reset                  See cvd reset --help. Requires cvd >= v1.2
  snapshot_take          cvd snapshot_take --help. Requires crosvm, cvd >= v1.4
  suspend                cvd suspend --help. Requires crosvm, cvd >= v1.4
  resume                 cvd resume --help. Requires crosvm, cvd >= v1.4
)";

class CvdHelpHandler : public CvdServerHandler {
 public:
  CvdHelpHandler(CommandSequenceExecutor& executor) : executor_(executor) {}

  Result<bool> CanHandle(const RequestWithStdio& request) const override {
    auto invocation = ParseInvocation(request.Message());
    return (invocation.command == "help");
  }

  Result<cvd::Response> Handle(const RequestWithStdio& request) override {
    std::unique_lock interrupt_lock(interruptible_);
    if (interrupted_) {
      return CF_ERR("Interrupted");
    }

    cvd::Response response;
    response.mutable_command_response();  // Sets oneof member
    response.mutable_status()->set_code(cvd::Status::OK);

    CF_EXPECT(CanHandle(request));

    auto [subcmd, subcmd_args] = ParseInvocation(request.Message());
    const auto supported_subcmd_list = executor_.CmdList();

    /*
     * cvd help, cvd help invalid_token, cvd help help
     */
    if (subcmd_args.empty() ||
        !Contains(supported_subcmd_list, subcmd_args.front()) ||
        subcmd_args.front() == "help") {
      WriteAll(request.Out(), kHelpMessage);
      return response;
    }

    cvd::Request modified_proto = HelpSubcommandToFlag(request);

    RequestWithStdio inner_cmd(request.Client(), modified_proto,
                               request.FileDescriptors(),
                               request.Credentials());

    interrupt_lock.unlock();
    CF_EXPECT(
        executor_.Execute({inner_cmd}, SharedFD::Open("/dev/null", O_RDWR)));

    return response;
  }

  Result<void> Interrupt() override {
    std::scoped_lock interrupt_lock(interruptible_);
    interrupted_ = true;
    CF_EXPECT(executor_.Interrupt());
    return {};
  }

  cvd_common::Args CmdList() const override { return {"help"}; }

 private:
  cvd::Request HelpSubcommandToFlag(const RequestWithStdio& request);

  std::mutex interruptible_;
  bool interrupted_ = false;
  CommandSequenceExecutor& executor_;
};

cvd::Request CvdHelpHandler::HelpSubcommandToFlag(
    const RequestWithStdio& request) {
  cvd::Request modified_proto = request.Message();
  auto all_args =
      cvd_common::ConvertToArgs(modified_proto.command_request().args());
  auto& args = *modified_proto.mutable_command_request()->mutable_args();
  args.Clear();
  // there must be one or more "help" in all_args
  // delete the first "help"
  bool found_help = false;
  for (const auto& cmd_arg : all_args) {
    if (cmd_arg != "help" || found_help) {
      args.Add(cmd_arg.c_str());
      continue;
    }
    // skip first help
    found_help = true;
  }
  args.Add("--help");
  return modified_proto;
}

std::unique_ptr<CvdServerHandler> NewCvdHelpHandler(
    CommandSequenceExecutor& executor) {
  return std::unique_ptr<CvdServerHandler>(new CvdHelpHandler(executor));
}

}  // namespace cuttlefish
